/* Filename  : mapping.c
 * 
 * Author    : Shang Wentao
 * Email     : wentaoshang@gmail.com
 * Date      : May 11, 2009
 * 
 * This file contains all the functions and global variables 
 * needed for the port mapping operation. It is not part of 
 * the original Passthru NDIS driver. 
 *
 */

#include "precomp.h"
#pragma hdrstop

NDIS_SPIN_LOCK          MapListLock;
MAP_NODE                Head;
LARGE_INTEGER           TimeOut = { { 1640261632, 8 } };     // equals to 1 hour
INT                     AutoConfig = 1;

USHORT       Mod = 256;
USHORT       Res = 1;

//
// Some basic algorithms needed
// 

//
// Change the byte order of a short integer
// from network order to host order.
//
USHORT ntohs( USHORT number )
{
	USHORT ret = ( number & 0x00FF ) << 8;
	ret += (number & 0xFF00 ) >> 8;
	return ret;
}

//
// Change the byte order of a short integer
// from host order to network order. It's 
// actually the same with ntohs().
//
USHORT htons( USHORT number )
{
	USHORT ret = ( number & 0x00FF ) << 8;
	ret += (number & 0xFF00 ) >> 8;
	return ret;
}

// 
// Update checksum according to old and new port number,
// all parameters (including the checksum) must be in host
// byte order, while the new checksum returned is in network 
// byte order and do not need to be re-ordered.
//
USHORT checksumadjust( USHORT chksum, USHORT oldp, USHORT newp )
{
     long x;
     USHORT ret;

     x = chksum;
     x = ~x & 0xffff;
     x -= oldp & 0xffff;
     if( x <= 0 ) {
	     x--;
	     x &= 0xffff;
     }

     x += newp & 0xffff;
     if( x & 0x10000 ) {
	     x++;
	     x &= 0xffff;
     }

     x = ~x & 0xffff;
     ret = x/256;
     ret += (x & 0xff) << 8;
     return ret;
}

// 
// Map list manipulate functions
//

//
// Initiate the list "head" by pointing head->pNext to NULL.
//
VOID init_list( PMAP_NODE head )
{
	if( head == NULL ) {
		DBGPRINT(("==> init_list: NULL list head!\n"));
		return;
	}
	head->pNext = NULL;
}

//
// Get the node number of the list.
//
UINT get_list_size( PMAP_NODE head )
{
	PMAP_NODE iter;
	UINT ret;
	
	if( head == NULL ) {
		DBGPRINT(("==> list_size: NULL list head!!\n"));
		return 0;
	}
	
	ret = 0;
	iter = head->pNext;
	NdisAcquireSpinLock(&MapListLock);
	while( iter != NULL ) {
		ret++;
		iter = iter->pNext;
	}
	NdisReleaseSpinLock(&MapListLock);
	return ret;
}

//
// Check whether a port is already in use, 
// return 1 if it is in use, else return 0.
//
INT port_in_use( USHORT port, PMAP_NODE head )
{
	PMAP_NODE    iter;
	INT          ret;
	
	if( head == NULL ) {
		DBGPRINT(("==> port_in_use: NULL list head!!\n"));
		return 0;
	}
	
	iter = head->pNext;
	ret = 0;
	
	NdisAcquireSpinLock(&MapListLock);
	while( iter != NULL ) {
		if( iter->newport == port ) {
			ret = 1;
			break;
		}
		iter = iter->pNext;
	}
	NdisReleaseSpinLock(&MapListLock);
	
	return ret;
}

// 
// Add node at list front, so the new map is always at the first node.
//
VOID list_add( PMAP_NODE node, PMAP_NODE head )
{
	if( node == NULL) {
		DBGPRINT(("==> list_add: NULL node!\n"));
		return;
	}
	if( head == NULL) {
		DBGPRINT(("==> list_add: NULL list head!\n"));
		return;
	}
	
	NdisAcquireSpinLock(&MapListLock);
	// add at front
	node->pNext = head->pNext;
	head->pNext = node;
	
	NdisReleaseSpinLock(&MapListLock);
}

//
// Allocate memory for a new map and add it to the list.
//
PMAP_NODE add_new_map( USHORT oldp, USHORT newp, PMAP_NODE head )
{
	NDIS_STATUS Status;
	PMAP_NODE node;
	
	if( head == NULL) {
		DBGPRINT(("==> add_new_map: NULL list head!\n"));
		return NULL;
	}
	
	
	Status = NdisAllocateMemoryWithTag( (PVOID)&node, sizeof( MAP_NODE ), TAG );
	if( Status != NDIS_STATUS_SUCCESS ) {
  	DBGPRINT(("==> add_new_map: NdisAllocateMemory failed!\n"));
		return NULL;
	}
	
	node->oldport = oldp;
	node->newport = newp;
	NdisGetCurrentSystemTime( &node->timer );
	
	list_add( node, head );
	DBGPRINT(("==> New Port Map Added: %d -> %d\n", oldp, newp ));
	
	return node;
}

//
// Check whether a map is time-out, return 1 if time out.
// Note that we assume that the first parameter is larger 
// than the second, that is, the first is newer and the 
// second is earlier. We perform no check for this.
//
INT is_time_out( PLARGE_INTEGER t1, PLARGE_INTEGER t2 )  // assume that t1 is larger than t2
{   
    if( t1->QuadPart - t2->QuadPart >= TimeOut.QuadPart )
        return 1;
    else
        return 0;
}

//
// Go through the whole list and remove the time-out maps.
//
VOID refresh_list( PMAP_NODE head )
{
	PMAP_NODE iter;
	PMAP_NODE prev;
	LARGE_INTEGER now;
	
	if( head == NULL) {
		DBGPRINT(("==> refresh_list: NULL list head!\n"));
		return;
	}
	
	NdisGetCurrentSystemTime( &now );
	
	iter = head->pNext;
	prev = head;
	NdisAcquireSpinLock(&MapListLock);
	while( iter != NULL ) {
		if( is_time_out( &now, &iter->timer ) == 1 ) {
			// Time out! Delete node!
			prev->pNext = iter->pNext;
			DBGPRINT(("==> Map %d -> %d time out! Delete!\n", iter->oldport, iter->newport ));
			NdisFreeMemory( iter, sizeof(MAP_NODE), 0 );
			iter = prev->pNext;
		}
		else {
			prev = iter;
			iter = iter->pNext;
		}
	}
	NdisReleaseSpinLock(&MapListLock);
}

//
// Delete all nodes in the list and free the memory.
//
VOID free_list( PMAP_NODE head )
{
	PMAP_NODE iter;
	
	if( head == NULL) {
		DBGPRINT(("==> free_list: NULL list head!\n"));
		return;
	}

	iter = head->pNext;
	
	NdisAcquireSpinLock(&MapListLock);
	while( iter != NULL ) {
		head->pNext = iter->pNext;
		DBGPRINT(("==> Map %d -> %d deleted!\n", iter->oldport, iter->newport ));
		NdisFreeMemory( iter, sizeof(MAP_NODE), 0 );
		iter = head->pNext;
	}
	NdisReleaseSpinLock(&MapListLock);
}


//
// Packet manipulation
//

//
// Get the map port for the outflow packet, return 0 if failed.
//
USHORT get_out_map_port( USHORT oldp, PMAP_NODE head, USHORT Mod, USHORT Res )
{
	PMAP_NODE iter;
	USHORT    ret;
	SHORT     remaining;
	USHORT    MaxPorts;
	
	MaxPorts = 65536 / Mod;
	//DBGPRINT(("==> Mod: %d\n", Mod ));
	//DBGPRINT(("==> MaxPorts: %d\n", MaxPorts ));
	//DBGPRINT(("==> Res: %d\n", Res ));

	if( head == NULL) {
		DBGPRINT(("==> get_out_map_port: NULL list head!\n"));
		return 0;
	}
	
	refresh_list(head);
	
	if( get_list_size(head) >= MaxPorts ) {
		DBGPRINT(("==> get_out_map_port: list full! Map port use up!\n"));
		return 0;
	}

	ret = 0;
	iter = head->pNext;
	if( iter != NULL ) {
		NdisAcquireSpinLock(&MapListLock);
		while( iter != NULL ) {
			if( iter->oldport == oldp ) {
				ret = iter->newport;
				NdisGetCurrentSystemTime( &iter->timer );
				DBGPRINT(("==> get_out_map_port: Find Map %d -> %d\n", oldp, ret ));
				break;
			}
			iter = iter->pNext;
		}
		NdisReleaseSpinLock(&MapListLock);
	}
	
	if( ret == 0 ) {
		USHORT    rover;
		USHORT    low;
		USHORT    high;
	
		low = (USHORT)(1024 / Mod) + 1;
		high = MaxPorts - 1;
		remaining = (high - low) + 1;

		iter = head->pNext;
		if( iter != NULL )
			rover = (USHORT)(iter->newport / Mod) + 1;
		else
			rover = low;
		
		do {
			ret = rover * Mod + Res;
			if( port_in_use(ret, head) == 0 )
				break;
			rover++;
			if( rover > high )
				rover = low;
			
			remaining--;
		}
		while( remaining > 0 );
		
		if( remaining <= 0 )
			return 0;
		
		if( add_new_map( oldp, ret, head ) == NULL )
			return 0;
		
	}	
	return ret;
}

//
// Get the original port for the inflow packet, return 0 if failed.
//
USHORT get_in_map_port( USHORT newp, PMAP_NODE head )
{
	PMAP_NODE iter;
	USHORT ret;
	
	if( head == NULL) {
		DBGPRINT(("==> get_in_map_port: NULL list head!\n"));
		return 0;
	}
	
	refresh_list(head);
	
	if( head->pNext == NULL )
		return 0;
	
	ret = 0;
	iter = head->pNext;
	NdisAcquireSpinLock(&MapListLock);
	while( iter != NULL ) {
		if( iter->newport == newp ) {
			ret = iter->oldport;
			NdisGetCurrentSystemTime( &iter->timer );
			DBGPRINT(("==> get_in_map_port: Find Map %d -> %d\n", ret, newp ));
			break;
		}
		iter = iter->pNext;
	}
	NdisReleaseSpinLock(&MapListLock);
	return ret;
}

